import { uploadPartOneRequest, uploadPartTwoRequest } from "@/api/file";
/**
 * 上传失败处理机制
 * 秒传处理，跳过已上传的-接口
 * 断点续传，提交未上传的-接口
 */

/**
 * FileUploaderConfig 接口定义了文件上传器的配置项
 * - file: 待上传的文件
 * - chunkSize: 分片大小，默认为 1MB
 * - concurrentUploads: 并发上传数量，默认根据浏览器类型设置
 * - isGenerateChunkHash: 是否生成分片哈希
 */

/**
 * ChunkInfo 接口定义了分片的信息
 * - chunk: 分片的 Blob 数据
 * - index: 分片的索引
 * - chunkHash: 分片的哈希值
 * - status: 分片的状态 ('waiting' | 'uploading' | 'uploaded' | 'failed')
 */

/**
 * ChunkStatus 枚举定义了分片的状态值
 * - Waiting: 等待上传
 * - Uploading: 上传中
 * - Uploaded: 已上传
 * - Failed: 上传失败
 */

/**
 * FileUploader 类定义了文件上传器的实现
 */
export default class FileUploader {
  file; // 上传文件
  chunkSize; // 分片大小
  chunks = []; // 分片队列
  worker;
  isUploading = false; // 正在上传
  concurrentUploads; // 浏览器并发数
  pauseFlag = false; // 暂停
  isGenerateChunkHash = false; // 分片文件是否生成hash
  isGenerateFileHash = false; // 文件是否生成hash
  taskId; // 任务id
  path; // 文件路径
  fileHash; // 文件哈希值
  retryNum; // 最多可重试次数
  numSamples; // 取样条数，默认为零，则不取样
  isAutoSample; // 是否自动取样，默认为false
  unbrokenFileId; // 完整的文件id
  progress; // 文件上传进度
  timeout; // 文件上传超时时间
  label; // 文件标签
  /**
   * 构造函数接受 FileUploaderConfig 对象作为参数，初始化文件上传器的配置
   */
  constructor(config) {
    const {
      file,
      chunkSize = 1024 * 1024,
      isGenerateChunkHash = false,
      path = "",
      retryNum = 3,
      numSamples = 0,
      timeout = 60 * 1000 * 5,
      isGenerateFileHash = true,
      isAutoSample = false,
      label,
    } = config;

    if (!file) {
      throw new Error("file is must");
    }
    if (!file.size) {
      throw new Error("file is length 0");
    }
    this.file = file;
    this.chunkSize = chunkSize;
    this.concurrentUploads = this.getConcurrencyLimit();
    this.worker = this.createWorker();
    this.isGenerateChunkHash = isGenerateChunkHash;
    this.isGenerateFileHash = isGenerateFileHash;
    this.taskId = "";
    this.path = path;
    this.fileHash = "";
    this.unbrokenFileId = "";
    this.progress = 0;
    this.retryNum = retryNum;
    this.numSamples = numSamples;
    this.timeout = timeout;
    this.isAutoSample = isAutoSample;
    this.label = label;
  }

  /**
   * 根据浏览器类型返回并发上传限制数
   */
  getConcurrencyLimit() {
    const userAgent = navigator.userAgent;

    switch (true) {
      case /Chrome/i.test(userAgent):
        return 6;
      case /Firefox/i.test(userAgent):
        return 6;
      case /Safari/i.test(userAgent):
        return 6;
      case /Edge|Edg/i.test(userAgent):
        return 6;
      case /MSIE|Trident/i.test(userAgent):
        return 4;
      default:
        return 4;
    }
  }

  // 获取分片信息
  async getFragmentInformation() {
    const fileInfo = {
      chunkSize: this.chunkSize,
      fileName: this.file.name,
      fileSize: this.file.size,
      path: this.path,
      fileMD5: this.fileHash,
      label: this.label,
    };
    try {
      const res = await uploadPartOneRequest(fileInfo);
      const { fastUpload, parts, taskId, treeId } = res.data;

      // debugger;
      this.taskId = taskId;
      // 判断文件是否已上传完成
      if (fastUpload) {
        this.unbrokenFileId = treeId;
        this.setProgress(100);
        this.setUpload(false);
        return true;
      } else {
        // 继续上传文件分片
        if (parts && parts.length) {
          for (const item of this.chunks) {
            const { index } = item;
            parts.find((part) => {
              if (part.chunkSeq === index) {
                item.status = "uploaded";
                item.progress = 100;
                return true;
              }
            });
          }
        }
      }
    } catch (error) {
      this.setUpload(false);
      throw new Error(error);
    }
  }

  /**
   * 创建并返回一个用于处理分片上传的 Web Worker
   */
  createWorker() {
    console.log(
      new URL("../../../public/assets/upload-worker.js", import.meta.url)
    );
    return new Worker(
      new URL("../../../public/assets/upload-worker.js", import.meta.url)
    );
  }

  /**
   * 使用 Web Worker 处理文件，返回分片信息数组
   */
  async processFileInWorker(file) {
    return new Promise((resolve) => {
      this.worker.postMessage({
        file,
        chunkSize: this.chunkSize,
        isGenerateChunkHash: this.isGenerateChunkHash,
        isGenerateFileHash: this.isGenerateFileHash,
        numSamples: this.numSamples,
        retryNum: this.retryNum,
        isAutoSample: this.isAutoSample,
      });
      this.worker.onmessage = (event) => {
        const { chunks, fileHash } = event.data;
        this.fileHash = fileHash;
        resolve(chunks);
      };
    });
  }

  /**
   * 开始文件处理和上传，初始化分片信息，设置状态为等待上传，并调用 uploadNextChunk 方法
   */
  async startFileProcessing() {
    this.chunks = await this.processFileInWorker(this.file);

    for (let index = 0; index < this.chunks.length; index++) {
      const chunkInfo = this.chunks[index];
      // 绑定上传方法，并设置状态为等待上传
      const uploadPromise = this.uploadChunk.bind(this, chunkInfo);
      this.chunks[index].uploadChunk = uploadPromise;
      this.chunks[index].status = "waiting";
    }
    // 获取分片信息
    const complete = await this.getFragmentInformation();
    if (complete) return;
    // 开始上传第一个分片
    this.uploadNextChunk();
    // 更新进度，秒传的情况
    this.handleUploadProgress();
  }

  /**
   * 上传下一个等待中的分片
   */
  async uploadNextChunk() {
    let effective = 0;
    for (let index = 0; index < this.chunks.length; index++) {
      // 控制并发上传数量
      if (
        this.chunks[index].status === "waiting" &&
        effective < this.concurrentUploads
      ) {
        effective++;
        // 调用上传方法
        this.chunks[index]?.uploadChunk();
      }
    }
  }

  /**
   * 上传单个分片
   */
  async uploadChunk(chunkInfo) {
    const { chunk, index, chunkHash } = chunkInfo;

    // 检查分片状态，避免重复上传
    if (chunkInfo.status !== "waiting") {
      console.log(`块 ${index} 状态为 ${chunkInfo.status}，跳过`);
      return;
    }

    // 更新状态为上传中
    this.updateChunkStatus(index, "uploading");

    // 创建 FormData 对象，将分片信息添加到表单中
    const formData = new FormData();
    formData.append("file", chunk);
    formData.append("chunkNumber", index.toString());
    formData.append("chunkHash", chunkHash);
    formData.append("taskId", this.taskId);

    try {
      await uploadPartTwoRequest(formData, {
        timeout: this.timeout, // 设置超时时间，避免上传过慢导致任务超时
        onUploadProgress: (res) => {
          // debugger;
          // chunkInfo.progress = res.progress * 100;
        },
      });
      this.updateChunkStatus(index, "uploaded");
      // 请求上传下一个分片
      this.requestNextPost(index);
    } catch (error) {
      console.error("上传错误:", error);
      // 更新状态为上传失败
      this.updateChunkStatus(index, "failed");
      // 请求上传下一个分片
      this.requestNextPost(index);
    }
  }

  /**
   * 更新分片的状态
   */
  updateChunkStatus(chunkIndex, status) {
    const chunkInfo = this.chunks[chunkIndex - 1];
    chunkInfo.status = status;
  }

  /**
   * 异常重试机制
   */

  exceptionRetry() {
    let exec = false;
    this.chunks.forEach((item) => {
      if (item.status === "failed" && item.retryNumed < item.retryNum) {
        exec = true;
        item.status = "waiting";
        item.progress = 0;
        item.retryNumed++;
      }
    });
    console.log(`是否${exec}执行重试机制`, this.chunks);

    // 再次执行
    exec && this.uploadNextChunk();
  }

  /**
   * 请求上传下一个分片
   */
  requestNextPost(chunkIndex) {
    // 更新进度
    this.handleUploadProgress();

    // 是否暂停
    if (this.pauseFlag) {
      console.log("暂停中");
      return;
    }

    // 处理上传错误，可能需要重试等操作
    if (chunkIndex >= this.chunks.length) {
      this.exceptionRetry();
    } else {
      // 寻找下一个等待中的分片并上传
      this.chunks.find((item) => {
        if (item.status === "waiting") {
          item.uploadChunk();
          return true;
        }
      });
    }
  }

  setUpload(status) {
    this.isUploading = status;
  }

  setProgress(num) {
    this.progress = Math.floor(num);
  }

  /**
   * 开始上传，触发文件处理和上传流程
   */
  startUpload() {
    if (this.isUploading) return;
    this.setUpload(true);

    // 开始文件处理和上传
    this.startFileProcessing();
  }

  /**
   * 暂停上传
   */
  pauseUpload() {
    this.pauseFlag = true;
    this.setUpload(false);
  }

  /**
   * 恢复上传
   */
  resumeUpload() {
    if (this.isUploading) return;

    this.pauseFlag = false;
    this.setUpload(true);

    this.uploadNextChunk();
  }

  getFileId() {
    return this.unbrokenFileId;
  }

  /**
   * 处理上传进度，可以更新UI上的进度条
   */
  handleUploadProgress() {
    let num = 0;
    let uploadingProgress = 0;
    for (const item of this.chunks) {
      if (item.status === "uploaded") {
        num++;
      }
      if (item.status === "uploading") {
        uploadingProgress += item.progress * (1 / this.chunks.length);
      }
    }
    const progress = (num / this.chunks.length) * 100 + uploadingProgress;
    // debugger;
    this.setProgress(progress);
    console.log(this.progress);
    // 上传完成
    if (num === this.chunks.length) {
      this.getFragmentInformation();
    }
  }
}
